Completed
Pull Request — master (#89)
by thomas
53s
created

Wallet.pay   D

Complexity

Conditions 10
Paths 224

Size

Total Lines 82

Duplication

Lines 16
Ratio 19.51 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 10
c 2
b 0
f 0
nc 224
dl 16
loc 82
rs 4.6178
nop 8

1 Function

Rating   Name   Duplication   Size   Complexity  
B 0 41 1

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like Wallet.pay often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
var _ = require('lodash');
2
var assert = require('assert');
3
var q = require('q');
4
var async = require('async');
5
var bitcoin = require('bitcoinjs-lib');
6
var bitcoinMessage = require('bitcoinjs-message');
7
var blocktrail = require('./blocktrail');
8
var CryptoJS = require('crypto-js');
9
var Encryption = require('./encryption');
10
var EncryptionMnemonic = require('./encryption_mnemonic');
11
var SizeEstimation = require('./size_estimation');
12
var bip39 = require('bip39');
13
14
var SignMode = {
15
    SIGN: "sign",
16
    DONT_SIGN: "dont_sign"
17
};
18
19
/**
20
 *
21
 * @param sdk                   APIClient       SDK instance used to do requests
22
 * @param identifier            string          identifier of the wallet
23
 * @param walletVersion         string
24
 * @param primaryMnemonic       string          primary mnemonic
25
 * @param encryptedPrimarySeed
26
 * @param encryptedSecret
27
 * @param primaryPublicKeys     string          primary mnemonic
28
 * @param backupPublicKey       string          BIP32 master pubKey M/
29
 * @param blocktrailPublicKeys  array           list of blocktrail pubKeys indexed by keyIndex
30
 * @param keyIndex              int             key index to use
31
 * @param segwit                int             segwit toggle from server
32
 * @param testnet               bool            testnet
33
 * @param checksum              string
34
 * @param upgradeToKeyIndex     int
35
 * @param bypassNewAddressCheck bool            flag to indicate if wallet should/shouldn't derive new address locally to verify api
36
 * @constructor
37
 * @internal
38
 */
39
var Wallet = function(
40
    sdk,
41
    identifier,
42
    walletVersion,
43
    primaryMnemonic,
44
    encryptedPrimarySeed,
45
    encryptedSecret,
46
    primaryPublicKeys,
47
    backupPublicKey,
48
    blocktrailPublicKeys,
49
    keyIndex,
50
    segwit,
51
    testnet,
52
    checksum,
53
    upgradeToKeyIndex,
54
    bypassNewAddressCheck
55
) {
56
    /* jshint -W071 */
57
    var self = this;
58
59
    self.sdk = sdk;
60
    self.identifier = identifier;
61
    self.walletVersion = walletVersion;
62
    self.locked = true;
63
    self.bypassNewAddressCheck = !!bypassNewAddressCheck;
64
    self.bitcoinCash = self.sdk.bitcoinCash;
65
    self.segwit = !!segwit;
66
    assert(!self.segwit || !self.bitcoinCash);
67
68
    self.testnet = testnet;
69
    if (self.bitcoinCash) {
70
        if (self.testnet) {
71
            self.network = bitcoin.networks.bitcoincashtestnet;
72
        } else {
73
            self.network = bitcoin.networks.bitcoincash;
74
        }
75
    } else {
76
        if (self.testnet) {
77
            self.network = bitcoin.networks.testnet;
78
        } else {
79
            self.network = bitcoin.networks.bitcoin;
80
        }
81
    }
82
83
    assert(backupPublicKey instanceof bitcoin.HDNode);
84
    assert(_.every(primaryPublicKeys, function(primaryPublicKey) { return primaryPublicKey instanceof bitcoin.HDNode; }));
85
    assert(_.every(blocktrailPublicKeys, function(blocktrailPublicKey) { return blocktrailPublicKey instanceof bitcoin.HDNode; }));
86
87
    // v1
88
    self.primaryMnemonic = primaryMnemonic;
89
90
    // v2 & v3
91
    self.encryptedPrimarySeed = encryptedPrimarySeed;
92
    self.encryptedSecret = encryptedSecret;
93
94
    self.primaryPrivateKey = null;
95
    self.backupPrivateKey = null;
96
97
    self.backupPublicKey = backupPublicKey;
98
    self.blocktrailPublicKeys = blocktrailPublicKeys;
99
    self.primaryPublicKeys = primaryPublicKeys;
100
    self.keyIndex = keyIndex;
101
102
    if (!self.bitcoinCash) {
103
        if (self.segwit) {
104
            self.chain = Wallet.CHAIN_BTC_DEFAULT;
105
            self.changeChain = Wallet.CHAIN_BTC_SEGWIT;
106
        } else {
107
            self.chain = Wallet.CHAIN_BTC_DEFAULT;
108
            self.changeChain = Wallet.CHAIN_BTC_DEFAULT;
109
        }
110
    } else {
111
        self.chain = Wallet.CHAIN_BCC_DEFAULT;
112
        self.changeChain = Wallet.CHAIN_BCC_DEFAULT;
113
    }
114
115
    self.checksum = checksum;
116
    self.upgradeToKeyIndex = upgradeToKeyIndex;
117
118
    self.secret = null;
119
    self.seedHex = null;
120
};
121
122
Wallet.WALLET_VERSION_V1 = 'v1';
123
Wallet.WALLET_VERSION_V2 = 'v2';
124
Wallet.WALLET_VERSION_V3 = 'v3';
125
126
Wallet.WALLET_ENTROPY_BITS = 256;
127
128
Wallet.OP_RETURN = 'opreturn';
129
Wallet.DATA = Wallet.OP_RETURN; // alias
130
131
Wallet.PAY_PROGRESS_START = 0;
132
Wallet.PAY_PROGRESS_COIN_SELECTION = 10;
133
Wallet.PAY_PROGRESS_CHANGE_ADDRESS = 20;
134
Wallet.PAY_PROGRESS_SIGN = 30;
135
Wallet.PAY_PROGRESS_SEND = 40;
136
Wallet.PAY_PROGRESS_DONE = 100;
137
138
Wallet.CHAIN_BTC_DEFAULT = 0;
139
Wallet.CHAIN_BTC_SEGWIT = 2;
140
Wallet.CHAIN_BCC_DEFAULT = 1;
141
142
Wallet.FEE_STRATEGY_FORCE_FEE = blocktrail.FEE_STRATEGY_FORCE_FEE;
143
Wallet.FEE_STRATEGY_BASE_FEE = blocktrail.FEE_STRATEGY_BASE_FEE;
144
Wallet.FEE_STRATEGY_OPTIMAL = blocktrail.FEE_STRATEGY_OPTIMAL;
145
Wallet.FEE_STRATEGY_LOW_PRIORITY = blocktrail.FEE_STRATEGY_LOW_PRIORITY;
146
Wallet.FEE_STRATEGY_MIN_RELAY_FEE = blocktrail.FEE_STRATEGY_MIN_RELAY_FEE;
147
148
Wallet.prototype.isSegwit = function() {
149
    return !!this.segwit;
150
};
151
152
Wallet.prototype.unlock = function(options, cb) {
153
    var self = this;
154
155
    var deferred = q.defer();
156
    deferred.promise.nodeify(cb);
157
158
    // avoid modifying passed options
159
    options = _.merge({}, options);
160
161
    q.fcall(function() {
162
        switch (self.walletVersion) {
163
            case Wallet.WALLET_VERSION_V1:
0 ignored issues
show
Bug introduced by
The variable Wallet seems to be never declared. If this is a global, consider adding a /** global: Wallet */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
164
                return self.unlockV1(options);
165
166
            case Wallet.WALLET_VERSION_V2:
167
                return self.unlockV2(options);
168
169
            case Wallet.WALLET_VERSION_V3:
170
                return self.unlockV3(options);
171
172
            default:
173
                return q.reject(new blocktrail.WalletInitError("Invalid wallet version"));
174
        }
175
    }).then(
176
        function(primaryPrivateKey) {
177
            self.primaryPrivateKey = primaryPrivateKey;
178
179
            // create a checksum of our private key which we'll later use to verify we used the right password
180
            var checksum = self.primaryPrivateKey.getAddress();
181
182
            // check if we've used the right passphrase
183
            if (checksum !== self.checksum) {
184
                throw new blocktrail.WalletChecksumError("Generated checksum [" + checksum + "] does not match " +
185
                    "[" + self.checksum + "], most likely due to incorrect password");
186
            }
187
188
            self.locked = false;
189
190
            // if the response suggests we should upgrade to a different blocktrail cosigning key then we should
191
            if (typeof self.upgradeToKeyIndex !== "undefined" && self.upgradeToKeyIndex !== null) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if typeof self.upgradeToKey...radeToKeyIndex !== null is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
192
                return self.upgradeKeyIndex(self.upgradeToKeyIndex);
193
            }
194
        }
195
    ).then(
196
        function(r) {
197
            deferred.resolve(r);
198
        },
199
        function(e) {
200
            deferred.reject(e);
201
        }
202
    );
203
204
    return deferred.promise;
205
};
206
207
Wallet.prototype.unlockV1 = function(options) {
208
    var self = this;
209
210
    options.primaryMnemonic = typeof options.primaryMnemonic !== "undefined" ? options.primaryMnemonic : self.primaryMnemonic;
211
    options.secretMnemonic = typeof options.secretMnemonic !== "undefined" ? options.secretMnemonic : self.secretMnemonic;
212
213
    return self.sdk.resolvePrimaryPrivateKeyFromOptions(options)
214
        .then(function(options) {
215
            self.primarySeed = options.primarySeed;
216
217
            return options.primaryPrivateKey;
218
        });
219
};
220
221
Wallet.prototype.unlockV2 = function(options, cb) {
222
    var self = this;
223
224
    var deferred = q.defer();
225
    deferred.promise.nodeify(cb);
226
227
    deferred.resolve(q.fcall(function() {
228
        /* jshint -W071, -W074 */
229
        options.encryptedPrimarySeed = typeof options.encryptedPrimarySeed !== "undefined" ? options.encryptedPrimarySeed : self.encryptedPrimarySeed;
230
        options.encryptedSecret = typeof options.encryptedSecret !== "undefined" ? options.encryptedSecret : self.encryptedSecret;
231
232
        if (options.secret) {
233
            self.secret = options.secret;
234
        }
235
236
        if (options.primaryPrivateKey) {
237
            throw new blocktrail.WalletDecryptError("specifying primaryPrivateKey has been deprecated");
238
        }
239
240
        if (options.primarySeed) {
241
            self.primarySeed = options.primarySeed;
242
        } else if (options.secret) {
243
            try {
244
                self.primarySeed = new Buffer(
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
245
                    CryptoJS.AES.decrypt(CryptoJS.format.OpenSSL.parse(options.encryptedPrimarySeed), self.secret).toString(CryptoJS.enc.Utf8), 'base64');
246
                if (!self.primarySeed.length) {
247
                    throw new Error();
248
                }
249
            } catch (e) {
250
                throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
251
            }
252
253
        } else {
254
            // avoid conflicting options
255
            if (options.passphrase && options.password) {
256
                throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
257
            }
258
            // normalize passphrase/password
259
            options.passphrase = options.passphrase || options.password;
260
261
            try {
262
                self.secret = CryptoJS.AES.decrypt(CryptoJS.format.OpenSSL.parse(options.encryptedSecret), options.passphrase).toString(CryptoJS.enc.Utf8);
263
                if (!self.secret.length) {
264
                    throw new Error();
265
                }
266
            } catch (e) {
267
                throw new blocktrail.WalletDecryptError("Failed to decrypt secret");
268
            }
269
            try {
270
                self.primarySeed = new Buffer(
271
                    CryptoJS.AES.decrypt(CryptoJS.format.OpenSSL.parse(options.encryptedPrimarySeed), self.secret).toString(CryptoJS.enc.Utf8), 'base64');
272
                if (!self.primarySeed.length) {
273
                    throw new Error();
274
                }
275
            } catch (e) {
276
                throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
277
            }
278
        }
279
280
        return bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
281
    }));
282
283
    return deferred.promise;
284
};
285
286
Wallet.prototype.unlockV3 = function(options, cb) {
287
    var self = this;
288
289
    var deferred = q.defer();
290
    deferred.promise.nodeify(cb);
291
292
    deferred.resolve(q.fcall(function() {
293
        return q.when()
294
            .then(function() {
295
                /* jshint -W071, -W074 */
296
                options.encryptedPrimarySeed = typeof options.encryptedPrimarySeed !== "undefined" ? options.encryptedPrimarySeed : self.encryptedPrimarySeed;
297
                options.encryptedSecret = typeof options.encryptedSecret !== "undefined" ? options.encryptedSecret : self.encryptedSecret;
298
299
                if (options.secret) {
300
                    self.secret = options.secret;
301
                }
302
303
                if (options.primaryPrivateKey) {
304
                    throw new blocktrail.WalletInitError("specifying primaryPrivateKey has been deprecated");
305
                }
306
307
                if (options.primarySeed) {
308
                    self.primarySeed = options.primarySeed;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
309
                } else if (options.secret) {
310
                    return self.sdk.promisedDecrypt(new Buffer(options.encryptedPrimarySeed, 'base64'), self.secret)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
311
                        .then(function(primarySeed) {
312
                            self.primarySeed = primarySeed;
313
                        }, function() {
314
                            throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
315
                        });
316
                } else {
317
                    // avoid conflicting options
318
                    if (options.passphrase && options.password) {
319
                        throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
320
                    }
321
                    // normalize passphrase/password
322
                    options.passphrase = options.passphrase || options.password;
323
                    delete options.password;
324
325
                    return self.sdk.promisedDecrypt(new Buffer(options.encryptedSecret, 'base64'), new Buffer(options.passphrase))
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
326
                        .then(function(secret) {
327
                            self.secret = secret;
328
                        }, function() {
329
                            throw new blocktrail.WalletDecryptError("Failed to decrypt secret");
330
                        })
331
                        .then(function() {
332
                            return self.sdk.promisedDecrypt(new Buffer(options.encryptedPrimarySeed, 'base64'), self.secret)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
333
                                .then(function(primarySeed) {
334
                                    self.primarySeed = primarySeed;
335
                                }, function() {
336
                                    throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed");
337
                                });
338
                        });
339
                }
340
            })
341
            .then(function() {
342
                return bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
343
            })
344
        ;
345
    }));
346
347
    return deferred.promise;
348
};
349
350
Wallet.prototype.lock = function() {
351
    var self = this;
352
353
    self.secret = null;
354
    self.primarySeed = null;
355
    self.primaryPrivateKey = null;
356
    self.backupPrivateKey = null;
357
358
    self.locked = true;
359
};
360
361
/**
362
 * upgrade wallet to V3 encryption scheme
363
 *
364
 * @param passphrase is required again to reencrypt the data, important that it's the correct password!!!
365
 * @param cb
366
 * @returns {promise}
367
 */
368
Wallet.prototype.upgradeToV3 = function(passphrase, cb) {
369
    var self = this;
370
371
    var deferred = q.defer();
372
    deferred.promise.nodeify(cb);
373
374
    q.when(true)
375
        .then(function() {
376
            if (self.locked) {
377
                throw new blocktrail.WalletLockedError("Wallet needs to be unlocked to upgrade");
378
            }
379
380
            if (self.walletVersion === Wallet.WALLET_VERSION_V3) {
381
                throw new blocktrail.WalletUpgradeError("Wallet is already V3");
382
            } else if (self.walletVersion === Wallet.WALLET_VERSION_V2) {
383
                return self._upgradeV2ToV3(passphrase, deferred.notify.bind(deferred));
384
            } else if (self.walletVersion === Wallet.WALLET_VERSION_V1) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if self.walletVersion === Wallet.WALLET_VERSION_V1 is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
385
                return self._upgradeV1ToV3(passphrase, deferred.notify.bind(deferred));
386
            }
387
        })
388
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
389
390
    return deferred.promise;
391
};
392
393
Wallet.prototype._upgradeV2ToV3 = function(passphrase, notify) {
394
    var self = this;
395
396
    return q.when(true)
397
        .then(function() {
398
            var options = {
399
                storeDataOnServer: true,
400
                passphrase: passphrase,
401
                primarySeed: self.primarySeed,
402
                recoverySecret: false // don't create new recovery secret, V2 already has ones
403
            };
404
405
            return self.sdk.produceEncryptedDataV3(options, notify || function noop() {})
406
                .then(function(options) {
407
                    return self.sdk.updateWallet(self.identifier, {
408
                        encrypted_primary_seed: options.encryptedPrimarySeed.toString('base64'),
409
                        encrypted_secret: options.encryptedSecret.toString('base64'),
410
                        wallet_version: Wallet.WALLET_VERSION_V3
411
                    }).then(function() {
412
                        self.secret = options.secret;
413
                        self.encryptedPrimarySeed = options.encryptedPrimarySeed;
414
                        self.encryptedSecret = options.encryptedSecret;
415
                        self.walletVersion = Wallet.WALLET_VERSION_V3;
416 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
417
                        return self;
418
                    });
419
                });
420
        });
421
422
};
423
424
Wallet.prototype._upgradeV1ToV3 = function(passphrase, notify) {
425
    var self = this;
426
427
    return q.when(true)
428
        .then(function() {
429
            var options = {
430
                storeDataOnServer: true,
431
                passphrase: passphrase,
432
                primarySeed: self.primarySeed
433
            };
434
435
            return self.sdk.produceEncryptedDataV3(options, notify || function noop() {})
436
                .then(function(options) {
437
                    // store recoveryEncryptedSecret for printing on backup sheet
438
                    self.recoveryEncryptedSecret = options.recoveryEncryptedSecret;
439
440
                    return self.sdk.updateWallet(self.identifier, {
441
                        primary_mnemonic: '',
442
                        encrypted_primary_seed: options.encryptedPrimarySeed.toString('base64'),
443
                        encrypted_secret: options.encryptedSecret.toString('base64'),
444
                        recovery_secret: options.recoverySecret.toString('hex'),
445
                        wallet_version: Wallet.WALLET_VERSION_V3
446
                    }).then(function() {
447
                        self.secret = options.secret;
448
                        self.encryptedPrimarySeed = options.encryptedPrimarySeed;
449
                        self.encryptedSecret = options.encryptedSecret;
450
                        self.walletVersion = Wallet.WALLET_VERSION_V3;
451
452
                        return self;
453
                    });
454
                });
455
        });
456
};
457
458
Wallet.prototype.doPasswordChange = function(newPassword) {
459
    var self = this;
460
461
    return q.when(null)
462
        .then(function() {
463
464
            if (self.walletVersion === Wallet.WALLET_VERSION_V1) {
465
                throw new blocktrail.WalletLockedError("Wallet version does not support password change!");
466
            }
467
468
            if (self.locked) {
469
                throw new blocktrail.WalletLockedError("Wallet needs to be unlocked to change password");
470
            }
471
472
            if (!self.secret) {
473
                throw new blocktrail.WalletLockedError("No secret");
474
            }
475
476
            var newEncryptedSecret;
477
            var newEncrypedWalletSecretMnemonic;
478
            if (self.walletVersion === Wallet.WALLET_VERSION_V2) {
479
                newEncryptedSecret = CryptoJS.AES.encrypt(self.secret, newPassword).toString(CryptoJS.format.OpenSSL);
480
                newEncrypedWalletSecretMnemonic = bip39.entropyToMnemonic(blocktrail.convert(newEncryptedSecret, 'base64', 'hex'));
481
482
            } else {
483
                if (typeof newPassword === "string") {
484
                    newPassword = new Buffer(newPassword);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
485
                } else {
486
                    if (!(newPassword instanceof Buffer)) {
487
                        throw new Error('New password must be provided as a string or a Buffer');
488
                    }
489
                }
490
491
                newEncryptedSecret = Encryption.encrypt(self.secret, newPassword);
492
                newEncrypedWalletSecretMnemonic = EncryptionMnemonic.encode(newEncryptedSecret);
493
494
                // It's a buffer, so convert it back to base64
495
                newEncryptedSecret = newEncryptedSecret.toString('base64');
496
            }
497
498
            return [newEncryptedSecret, newEncrypedWalletSecretMnemonic];
499
        });
500
};
501
502
Wallet.prototype.passwordChange = function(newPassword, cb) {
503
    var self = this;
504
505
    var deferred = q.defer();
506
    deferred.promise.nodeify(cb);
507
508
    q.fcall(function() {
509
        return self.doPasswordChange(newPassword)
510
            .then(function(r) {
511
                var newEncryptedSecret = r[0];
512
                var newEncrypedWalletSecretMnemonic = r[1];
513
514
                return self.sdk.updateWallet(self.identifier, {encrypted_secret: newEncryptedSecret}).then(function() {
515
                    self.encryptedSecret = newEncryptedSecret;
516
517
                    // backupInfo
518
                    return {
519
                        encryptedSecret: newEncrypedWalletSecretMnemonic
520
                    };
521
                });
522
            })
523
            .then(
524
                function(r) {
525
                    deferred.resolve(r);
526
                },
527
                function(e) {
528
                    deferred.reject(e);
529
                }
530
            );
531
    });
532
533
    return deferred.promise;
534
};
535
536
/**
537
 * get address for specified path
538
 *
539
 * @param path
540
 * @returns string
541
 */
542
Wallet.prototype.getAddressByPath = function(path) {
543
    return this.getWalletScriptByPath(path).address;
544
};
545
546
/**
547
 * get redeemscript for specified path
548 View Code Duplication
 *
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
549
 * @param path
550
 * @returns {bitcoin.Script}
551
 */
552
Wallet.prototype.getRedeemScriptByPath = function(path) {
553
    return this.getWalletScriptByPath(path).redeemScript;
554
};
555
556
Wallet.prototype.getWalletScriptByPath = function(path) {
557
    var self = this;
558
559
    // get derived primary key
560
    var derivedPrimaryPublicKey = self.getPrimaryPublicKey(path);
561
    // get derived blocktrail key
562
    var derivedBlocktrailPublicKey = self.getBlocktrailPublicKey(path);
563
    // derive the backup key
564
    var derivedBackupPublicKey = Wallet.deriveByPath(self.backupPublicKey, path.replace("'", ""), "M");
565
566
    // sort the pubkeys
567
    var pubKeys = Wallet.sortMultiSigKeys([
568
        derivedPrimaryPublicKey.keyPair.getPublicKeyBuffer(),
569
        derivedBackupPublicKey.keyPair.getPublicKeyBuffer(),
570
        derivedBlocktrailPublicKey.keyPair.getPublicKeyBuffer()
571
    ]);
572
573
    var multisig = bitcoin.script.multisig.output.encode(2, pubKeys);
574
    var scriptType = parseInt(path.split("/")[2]);
575
576
    var ws, rs;
577
    if (this.network !== "bitcoincash" && scriptType === Wallet.CHAIN_BTC_SEGWIT) {
578
        ws = multisig;
579
        rs = bitcoin.script.witnessScriptHash.output.encode(bitcoin.crypto.sha256(ws));
580
    } else {
581
        ws = null;
582
        rs = multisig;
583
    }
584
585
    var spk = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(rs));
586
    var addr = bitcoin.address.fromOutputScript(spk, this.network);
587
588
    return {
589
        witnessScript: ws,
590
        redeemScript: rs,
591
        scriptPubKey: spk,
592
        address: addr
593
    };
594
};
595
596
/**
597
 * get primary public key by path
598
 *  first level of the path is used as keyIndex to find the correct key in the dict
599
 *
600
 * @param path  string
601
 * @returns {bitcoin.HDNode}
602
 */
603
Wallet.prototype.getPrimaryPublicKey = function(path) {
604
    var self = this;
605
606
    path = path.replace("m", "M");
607
608
    var keyIndex = path.split("/")[1].replace("'", "");
609
610
    if (!self.primaryPublicKeys[keyIndex]) {
611
        if (self.primaryPrivateKey) {
612
            self.primaryPublicKeys[keyIndex] = Wallet.deriveByPath(self.primaryPrivateKey, "M/" + keyIndex + "'", "m");
613
        } else {
614
            throw new blocktrail.KeyPathError("Wallet.getPrimaryPublicKey keyIndex (" + keyIndex + ") is unknown to us");
615
        }
616
    }
617
618
    var primaryPublicKey = self.primaryPublicKeys[keyIndex];
619
    return Wallet.deriveByPath(primaryPublicKey, path, "M/" + keyIndex + "'");
620
};
621
622
/**
623
 * get blocktrail public key by path
624
 *  first level of the path is used as keyIndex to find the correct key in the dict
625
 *
626
 * @param path  string
627
 * @returns {bitcoin.HDNode}
628
 */
629
Wallet.prototype.getBlocktrailPublicKey = function(path) {
630
    var self = this;
631
632
    path = path.replace("m", "M");
633
634
    var keyIndex = path.split("/")[1].replace("'", "");
635
636
    if (!self.blocktrailPublicKeys[keyIndex]) {
637
        throw new blocktrail.KeyPathError("Wallet.getBlocktrailPublicKey keyIndex (" + keyIndex + ") is unknown to us");
638
    }
639
640
    var blocktrailPublicKey = self.blocktrailPublicKeys[keyIndex];
641
642
    return Wallet.deriveByPath(blocktrailPublicKey, path, "M/" + keyIndex + "'");
643
};
644
645
/**
646
 * upgrade wallet to different blocktrail cosign key
647
 *
648
 * @param keyIndex  int
649
 * @param [cb]      function
650
 * @returns {q.Promise}
651
 */
652
Wallet.prototype.upgradeKeyIndex = function(keyIndex, cb) {
653
    var self = this;
654
655
    var deferred = q.defer();
656
    deferred.promise.nodeify(cb);
657
658
    if (self.locked) {
659
        deferred.reject(new blocktrail.WalletLockedError("Wallet needs to be unlocked to upgrade key index"));
660
        return deferred.promise;
661
    }
662
663
    var primaryPublicKey = self.primaryPrivateKey.deriveHardened(keyIndex).neutered();
664
665
    deferred.resolve(
666
        self.sdk.upgradeKeyIndex(self.identifier, keyIndex, [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"])
667
            .then(function(result) {
668
                self.keyIndex = keyIndex;
669
                _.forEach(result.blocktrail_public_keys, function(publicKey, keyIndex) {
670
                    self.blocktrailPublicKeys[keyIndex] = bitcoin.HDNode.fromBase58(publicKey[0], self.network);
671
                });
672
673
                self.primaryPublicKeys[keyIndex] = primaryPublicKey;
674
675
                return true;
676
            })
677
    );
678
679
    return deferred.promise;
680
};
681
682
/**
683
 * generate a new derived private key and return the new address for it
684
 *
685
 * @param [chainIdx] int
686
 * @param [cb]  function        callback(err, address)
687
 * @returns {q.Promise}
688
 */
689
Wallet.prototype.getNewAddress = function(chainIdx, cb) {
690
    var self = this;
691
692
    // chainIdx is optional
693
    if (typeof chainIdx === "function") {
694
        cb = chainIdx;
695
        chainIdx = null;
696
    }
697
698
    var deferred = q.defer();
699
    deferred.promise.spreadNodeify(cb);
700
701
    // Only enter if it's not an integer
702
    if (chainIdx !== parseInt(chainIdx, 10)) {
703
        // deal with undefined or null, assume defaults
704
        if (typeof chainIdx === "undefined" || chainIdx === null) {
705
            chainIdx = self.chain;
706
        } else {
707
            // was a variable but not integer
708
            deferred.reject(new Error("Invalid chain index"));
709
            return deferred.promise;
710
        }
711
    }
712
713
    deferred.resolve(
714
        self.sdk.getNewDerivation(self.identifier, "M/" + self.keyIndex + "'/" + chainIdx)
715
            .then(function(newDerivation) {
716
                var path = newDerivation.path;
717
                var address = newDerivation.address;
718
                if (!self.bypassNewAddressCheck) {
719
720
                    address = self.getAddressByPath(newDerivation.path);
721
                    // debug check
722
                    if (address !== newDerivation.address) {
723
                        throw new blocktrail.WalletAddressError("Failed to verify address [" + newDerivation.address + "] !== [" + address + "]");
724
                    }
725
                }
726
727
                return [address, path];
728
            })
729
    );
730
731
    return deferred.promise;
732
};
733
734
/**
735
 * get the balance for the wallet
736
 *
737
 * @param [cb]  function        callback(err, confirmed, unconfirmed)
738
 * @returns {q.Promise}
739
 */
740
Wallet.prototype.getBalance = function(cb) {
741
    var self = this;
742
743
    var deferred = q.defer();
744
    deferred.promise.spreadNodeify(cb);
745
746
    deferred.resolve(
747
        self.sdk.getWalletBalance(self.identifier)
748
            .then(function(result) {
749
                return [result.confirmed, result.unconfirmed];
750
            })
751
    );
752
753
    return deferred.promise;
754
};
755
756
/**
757
 * get the balance for the wallet
758
 *
759
 * @param [cb]  function        callback(err, confirmed, unconfirmed)
760
 * @returns {q.Promise}
761
 */
762
Wallet.prototype.getInfo = function(cb) {
763
    var self = this;
764
765
    var deferred = q.defer();
766
    deferred.promise.spreadNodeify(cb);
767
768
    deferred.resolve(
769
        self.sdk.getWalletBalance(self.identifier)
770
    );
771
772
    return deferred.promise;
773
};
774
775
/**
776
 * do wallet discovery (slow)
777
 *
778
 * @param [gap] int             gap limit
779
 * @param [cb]  function        callback(err, confirmed, unconfirmed)
780
 * @returns {q.Promise}
781
 */
782
Wallet.prototype.doDiscovery = function(gap, cb) {
783
    var self = this;
784
785
    if (typeof gap === "function") {
786
        cb = gap;
787
        gap = null;
788
    }
789 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
790
    var deferred = q.defer();
791
    deferred.promise.spreadNodeify(cb);
792
793
    deferred.resolve(
794
        self.sdk.doWalletDiscovery(self.identifier, gap)
795
            .then(function(result) {
796
                return [result.confirmed, result.unconfirmed];
797
            })
798
    );
799
800
    return deferred.promise;
801
};
802
803
/**
804
 *
805
 * @param [force]   bool            ignore warnings (such as non-zero balance)
806
 * @param [cb]      function        callback(err, success)
807
 * @returns {q.Promise}
808
 */
809
Wallet.prototype.deleteWallet = function(force, cb) {
810
    var self = this;
811
812
    if (typeof force === "function") {
813
        cb = force;
814
        force = false;
815
    }
816
817
    var deferred = q.defer();
818
    deferred.promise.nodeify(cb);
819
820
    if (self.locked) {
821
        deferred.reject(new blocktrail.WalletDeleteError("Wallet needs to be unlocked to delete wallet"));
822
        return deferred.promise;
823
    }
824
825
    var checksum = self.primaryPrivateKey.getAddress();
826
    var privBuf = self.primaryPrivateKey.keyPair.d.toBuffer(32);
827
    var signature = bitcoinMessage.sign(checksum, self.network.messagePrefix, privBuf, true).toString('base64');
828
829
    deferred.resolve(
830
        self.sdk.deleteWallet(self.identifier, checksum, signature, force)
831
            .then(function(result) {
832
                return result.deleted;
833
            })
834
    );
835
836
    return deferred.promise;
837
};
838
839
/**
840
 * create, sign and send a transaction
841
 *
842
 * @param pay                   array       {'address': (int)value}     coins to send
843
 * @param [changeAddress]       bool        change address to use (auto generated if NULL)
844
 * @param [allowZeroConf]       bool        allow zero confirmation unspent outputs to be used in coin selection
845
 * @param [randomizeChangeIdx]  bool        randomize the index of the change output (default TRUE, only disable if you have a good reason to)
846
 * @param [feeStrategy]         string      defaults to Wallet.FEE_STRATEGY_OPTIMAL
847
 * @param [twoFactorToken]      string      2FA token
848
 * @param options
849
 * @param [cb]                  function    callback(err, txHash)
850
 * @returns {q.Promise}
851
 */
852
Wallet.prototype.pay = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, twoFactorToken, options, cb) {
853
854
    /* jshint -W071 */
855
    var self = this;
856
857
    if (typeof changeAddress === "function") {
858
        cb = changeAddress;
859
        changeAddress = null;
860
    } else if (typeof allowZeroConf === "function") {
861
        cb = allowZeroConf;
862
        allowZeroConf = false;
863
    } else if (typeof randomizeChangeIdx === "function") {
864
        cb = randomizeChangeIdx;
865
        randomizeChangeIdx = true;
866
    } else if (typeof feeStrategy === "function") {
867 View Code Duplication
        cb = feeStrategy;
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
868
        feeStrategy = null;
869
    } else if (typeof twoFactorToken === "function") {
870
        cb = twoFactorToken;
871
        twoFactorToken = null;
872
    } else if (typeof options === "function") {
873
        cb = options;
874
        options = {};
875
    }
876
877
    randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true;
878
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
879
    options = options || {};
880
    var checkFee = typeof options.checkFee !== "undefined" ? options.checkFee : true;
881
882
    var deferred = q.defer();
883
    deferred.promise.nodeify(cb);
884
885
    if (self.locked) {
886
        deferred.reject(new blocktrail.WalletLockedError("Wallet needs to be unlocked to send coins"));
887
        return deferred.promise;
888
    }
889
890
    q.nextTick(function() {
891
        deferred.notify(Wallet.PAY_PROGRESS_START);
892
        self.buildTransaction(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options)
893
            .then(
894
            function(r) { return r; },
895
            function(e) { deferred.reject(e); },
896
            function(progress) {
897
                deferred.notify(progress);
898
            }
899
        )
900
            .spread(
901
            function(tx, utxos) {
902
903
                deferred.notify(Wallet.PAY_PROGRESS_SEND);
904
905
                var data = {
906
                    signed_transaction: tx.toHex(),
907
                    base_transaction: tx.__toBuffer(null, null, false).toString('hex')
908
                };
909
910
                return self.sendTransaction(data, utxos.map(function(utxo) { return utxo['path']; }), checkFee, twoFactorToken, options.prioboost)
911
                    .then(function(result) {
912
                        deferred.notify(Wallet.PAY_PROGRESS_DONE);
913
914
                        if (!result || !result['complete'] || result['complete'] === 'false') {
915
                            deferred.reject(new blocktrail.TransactionSignError("Failed to completely sign transaction"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
916
                        } else {
917
                            return result['txid'];
918
                        }
919
                    });
920
            },
921
            function(e) {
922
                throw e;
923
            }
924
        )
925
            .then(
926
            function(r) { deferred.resolve(r); },
927
            function(e) { deferred.reject(e); }
928
        )
929
        ;
930
    });
931
932
    return deferred.promise;
933
};
934
935
Wallet.prototype.decodeAddress = function(address) {
936
    return Wallet.getAddressAndType(address, this.network);
937
};
938
939
Wallet.getAddressAndType = function(address, network) {
940
    var addr;
941
    var type;
942
    var err;
943
944
    if (network === bitcoin.networks.bitcoin || network === bitcoin.networks.testnet) {
945
        try {
946
            addr = bitcoin.address.fromBech32(address, network);
947
            err = null;
948
            type = "bech32";
949
950
        } catch (_err) {
951
            err = _err;
952
        }
953
954
        if (!err) {
955
            // Valid bech32 but invalid network immediately alerts
956
            if (addr.prefix !== network.bech32) {
957
                throw new blocktrail.InvalidAddressError("Address invalid on this network");
958
            }
959
        }
960
    }
961
962
    if (!addr) {
963
        try {
964
            addr = bitcoin.address.fromBase58Check(address);
965
            err = null;
966
            type = "base58";
967
        } catch (_err) {
968
            err = _err;
969
        }
970
971
        if (!err) {
972
            // Valid base58 but invalid network immediately alerts
973
            if (addr.version !== network.pubKeyHash && addr.version !== network.scriptHash) {
974
                throw new blocktrail.InvalidAddressError("Address invalid on this network");
975
            }
976
        }
977
    }
978
979
    if (err) {
980
        throw new blocktrail.InvalidAddressError(err.message);
981
    }
982
983
    return {
984
        address: address,
985
        decoded: addr,
986
        type: type
0 ignored issues
show
Bug introduced by
The variable type seems to not be initialized for all possible execution paths.
Loading history...
987
    };
988
};
989
990
Wallet.convertPayToOutputs = function(pay, network) {
991
    var send = [];
992
993
    var readFunc;
994
995
    // Deal with two different forms
996
    if (Array.isArray(pay)) {
997
        // output[]
998
        readFunc = function(i, output, obj) {
999
            if (typeof output !== "object") {
1000
                throw new Error("Invalid transaction output for numerically indexed list [1]");
1001
            }
1002
1003
            var keys = Object.keys(output);
1004
            if (keys.indexOf("scriptPubKey") !== -1 && keys.indexOf("value") !== -1) {
1005
                obj.scriptPubKey = output["scriptPubKey"];
1006
                obj.value = output["value"];
1007
            } else if (keys.indexOf("address") !== -1 && keys.indexOf("value") !== -1) {
1008
                obj.address = output["address"];
1009
                obj.value = output["value"];
1010
            } else if (keys.length !== 2 && output.length !== 2 && keys[0] !== 0 && keys[1] !== 1) {
1011
                obj.address = output[0];
1012
                obj.value = output[1];
1013
            } else {
1014
                throw new Error("Invalid transaction output for numerically indexed list [2]");
1015
            }
1016
        };
1017
    } else if (typeof pay === "object") {
1018
        // map[addr]amount
1019
        readFunc = function(address, value, obj) {
1020
            obj.address = address.trim();
1021
            obj.value = value;
1022
            if (obj.address === Wallet.OP_RETURN) {
1023
                var datachunk = Buffer.isBuffer(value) ? value : new Buffer(value, 'utf-8');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1024
                obj.scriptPubKey = bitcoin.script.nullData.output.encode(datachunk).toString('hex');
1025
                obj.value = 0;
1026
                obj.address = null;
1027
            }
1028
        };
1029
    } else {
1030
        throw new Error("Invalid input");
1031
    }
1032
1033
    Object.keys(pay).forEach(function(key) {
1034
        var obj = {};
1035
        readFunc(key, pay[key], obj);
1036
1037
        if (parseInt(obj.value, 10).toString() !== obj.value.toString()) {
1038
            throw new blocktrail.WalletSendError("Values should be in Satoshis");
1039
        }
1040
1041
        // Remove address, replace with scriptPubKey
1042
        if (typeof obj.address === "string") {
1043
            try {
1044
                var addrAndType = Wallet.getAddressAndType(obj.address, network);
1045
                obj.scriptPubKey = bitcoin.address.toOutputScript(addrAndType.address, network).toString('hex');
1046
                delete obj.address;
1047
            } catch (e) {
1048
                throw new blocktrail.InvalidAddressError("Invalid address [" + obj.address + "] (" + e.message + ")");
1049
            }
1050
        }
1051
1052
        // Extra checks when the output isn't OP_RETURN
1053
        if (obj.scriptPubKey.slice(0, 2) !== "6a") {
1054
            if (!(obj.value = parseInt(obj.value, 10))) {
1055
                throw new blocktrail.WalletSendError("Values should be non zero");
1056
            } else if (obj.value <= blocktrail.DUST) {
1057
                throw new blocktrail.WalletSendError("Values should be more than dust (" + blocktrail.DUST + ")");
1058
            }
1059
        }
1060
1061
        // Value fully checked now
1062
        obj.value = parseInt(obj.value, 10);
1063
1064
        send.push(obj);
1065
    });
1066
1067
    return send;
1068
};
1069
1070
Wallet.prototype.buildTransaction = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options, cb) {
1071
    /* jshint -W071 */
1072
    var self = this;
1073
1074
    if (typeof changeAddress === "function") {
1075
        cb = changeAddress;
1076
        changeAddress = null;
1077
    } else if (typeof allowZeroConf === "function") {
1078
        cb = allowZeroConf;
1079
        allowZeroConf = false;
1080
    } else if (typeof randomizeChangeIdx === "function") {
1081
        cb = randomizeChangeIdx;
1082
        randomizeChangeIdx = true;
1083
    } else if (typeof feeStrategy === "function") {
1084
        cb = feeStrategy;
1085
        feeStrategy = null;
1086
    } else if (typeof options === "function") {
1087
        cb = options;
1088
        options = {};
1089
    }
1090
1091
    randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true;
1092
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1093
    options = options || {};
1094
1095
    var deferred = q.defer();
1096
    deferred.promise.spreadNodeify(cb);
1097
1098
    q.nextTick(function() {
1099
        var send;
1100
        try {
1101
            send = Wallet.convertPayToOutputs(pay, self.network);
1102
        } catch (e) {
1103
            deferred.reject(e);
1104
            return deferred.promise;
1105
        }
1106
1107
        if (!send.length) {
1108
            deferred.reject(new blocktrail.WalletSendError("Need at least one recipient"));
1109
            return deferred.promise;
1110
        }
1111
1112
        deferred.notify(Wallet.PAY_PROGRESS_COIN_SELECTION);
1113
1114
        deferred.resolve(
1115
            self.coinSelection(send, true, allowZeroConf, feeStrategy, options)
1116
            /**
1117
             *
1118
             * @param {Object[]} utxos
1119
             * @param fee
1120
             * @param change
1121
             * @param randomizeChangeIdx
0 ignored issues
show
Documentation introduced by
The parameter randomizeChangeIdx does not exist. Did you maybe forget to remove this comment?
Loading history...
1122
             * @returns {*}
1123
             */
1124
                .spread(function(utxos, fee, change) {
1125
                    var tx, txb, outputs = [];
1126
1127
                    var deferred = q.defer();
1128
1129
                    async.waterfall([
1130
                        /**
1131
                         * prepare
1132
                         *
1133
                         * @param cb
1134
                         */
1135
                        function(cb) {
1136
                            var inputsTotal = utxos.map(function(utxo) {
1137
                                return utxo['value'];
1138
                            }).reduce(function(a, b) {
1139
                                return a + b;
1140
                            });
1141
                            var outputsTotal = send.map(function(output) {
1142
                                return output.value;
1143
                            }).reduce(function(a, b) {
1144
                                return a + b;
1145
                            });
1146
                            var estimatedChange = inputsTotal - outputsTotal - fee;
1147
1148
                            if (estimatedChange > blocktrail.DUST * 2 && estimatedChange !== change) {
1149
                                return cb(new blocktrail.WalletFeeError("the amount of change (" + change + ") " +
1150
                                    "suggested by the coin selection seems incorrect (" + estimatedChange + ")"));
1151
                            }
1152
1153
                            cb();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1154
                        },
1155
                        /**
1156
                         * init transaction builder
1157
                         *
1158
                         * @param cb
1159
                         */
1160
                        function(cb) {
1161
                            txb = new bitcoin.TransactionBuilder(self.network);
1162
                            if (self.bitcoinCash) {
1163
                                txb.enableBitcoinCash();
1164
                            }
1165
1166
                            cb();
1167
                        },
1168
                        /**
1169
                         * add UTXOs as inputs
1170
                         *
1171
                         * @param cb
1172
                         */
1173
                        function(cb) {
1174
                            var i;
1175
1176
                            for (i = 0; i < utxos.length; i++) {
1177
                                txb.addInput(utxos[i]['hash'], utxos[i]['idx']);
1178
                            }
1179
1180
                            cb();
1181
                        },
1182
                        /**
1183
                         * build desired outputs
1184
                         *
1185
                         * @param cb
1186
                         */
1187
                        function(cb) {
1188
                            send.forEach(function(_send) {
1189
                                if (_send.address) {
1190
                                    outputs.push({address: _send.address, value: _send.value});
1191
                                } else if (_send.scriptPubKey) {
1192
                                    outputs.push({scriptPubKey: new Buffer(_send.scriptPubKey, 'hex'), value: _send.value});
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1193
                                } else {
1194
                                    throw new Error("Invalid send");
1195
                                }
1196
                            });
1197
                            cb();
1198
                        },
1199
                        /**
1200
                         * get change address if required
1201
                         *
1202
                         * @param cb
1203
                         */
1204
                        function(cb) {
1205
                            if (change > 0) {
1206
                                if (change <= blocktrail.DUST) {
1207
                                    change = 0; // don't do a change output if it would be a dust output
1208
1209
                                } else {
1210
                                    if (!changeAddress) {
1211
                                        deferred.notify(Wallet.PAY_PROGRESS_CHANGE_ADDRESS);
1212
1213
                                        return self.getNewAddress(self.changeChain, function(err, address) {
1214
                                            if (err) {
1215
                                                return cb(err);
1216
                                            }
1217
                                            changeAddress = address;
1218
                                            cb();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1219
                                        });
1220
                                    }
1221
                                }
1222
                            }
1223
1224
                            cb();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1225
                        },
1226
                        /**
1227
                         * add change to outputs
1228
                         *
1229
                         * @param cb
1230
                         */
1231
                        function(cb) {
1232
                            if (change > 0) {
1233
                                if (randomizeChangeIdx) {
1234
                                    outputs.splice(_.random(0, outputs.length), 0, {
1235
                                        address: changeAddress,
1236
                                        value: change
1237
                                    });
1238
                                } else {
1239
                                    outputs.push({address: changeAddress, value: change});
1240
                                }
1241
                            }
1242
1243
                            cb();
1244
                        },
1245
                        /**
1246
                         * add outputs to txb
1247
                         *
1248
                         * @param cb
1249
                         */
1250
                        function(cb) {
1251
                            outputs.forEach(function(outputInfo) {
1252
                                txb.addOutput(outputInfo.scriptPubKey || outputInfo.address, outputInfo.value);
1253
                            });
1254
1255
                            cb();
1256
                        },
1257
                        /**
1258
                         * sign
1259
                         *
1260
                         * @param cb
1261
                         */
1262
                        function(cb) {
1263
                            var i, privKey, path, redeemScript, witnessScript;
1264
1265
                            deferred.notify(Wallet.PAY_PROGRESS_SIGN);
1266
1267
                            for (i = 0; i < utxos.length; i++) {
1268
                                var mode = SignMode.SIGN;
1269
                                if (utxos[i].sign_mode) {
1270
                                    mode = utxos[i].sign_mode;
1271
                                }
1272
1273
                                redeemScript = null;
0 ignored issues
show
Unused Code introduced by
The assignment to redeemScript seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
1274
                                witnessScript = null;
1275
                                if (mode === SignMode.SIGN) {
1276
                                    path = utxos[i]['path'].replace("M", "m");
1277
1278
                                    // todo: regenerate scripts for path and compare for utxo (paranoid mode)
1279
                                    if (self.primaryPrivateKey) {
1280
                                        privKey = Wallet.deriveByPath(self.primaryPrivateKey, path, "m").keyPair;
1281
                                    } else if (self.backupPrivateKey) {
1282
                                        privKey = Wallet.deriveByPath(self.backupPrivateKey, path.replace(/^m\/(\d+)\'/, 'm/$1'), "m").keyPair;
1283
                                    } else {
1284
                                        throw new Error("No master privateKey present");
1285
                                    }
1286
1287
                                    redeemScript = new Buffer(utxos[i]['redeem_script'], 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1288
                                    if (typeof utxos[i]['witness_script'] === 'string') {
1289
                                        witnessScript = new Buffer(utxos[i]['witness_script'], 'hex');
1290
                                    }
1291
1292
                                    var sigHash = bitcoin.Transaction.SIGHASH_ALL;
1293
                                    if (self.bitcoinCash) {
1294
                                        sigHash |= bitcoin.Transaction.SIGHASH_BITCOINCASHBIP143;
1295
                                    }
1296
1297
                                    txb.sign(i, privKey, redeemScript, sigHash, utxos[i].value, witnessScript);
1298
                                }
1299
                            }
1300
1301
                            tx = txb.buildIncomplete();
1302
1303
                            cb();
1304
                        },
1305
                        /**
1306
                         * estimate fee to verify that the API is not providing us wrong data
1307
                         *
1308
                         * @param cb
1309
                         */
1310
                        function(cb) {
1311
                            var estimatedFee = Wallet.estimateVsizeFee(tx, utxos);
1312
1313
                            if (self.sdk.feeSanityCheck) {
1314
                                switch (feeStrategy) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
1315
                                    case Wallet.FEE_STRATEGY_BASE_FEE:
0 ignored issues
show
Bug introduced by
The variable Wallet seems to be never declared. If this is a global, consider adding a /** global: Wallet */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1316
                                        if (Math.abs(estimatedFee - fee) > blocktrail.BASE_FEE) {
1317
                                            return cb(new blocktrail.WalletFeeError("the fee suggested by the coin selection (" + fee + ") " +
1318
                                                "seems incorrect (" + estimatedFee + ") for FEE_STRATEGY_BASE_FEE"));
1319
                                        }
1320
                                    break;
1321
1322
                                    case Wallet.FEE_STRATEGY_OPTIMAL:
1323
                                        if (fee > estimatedFee * self.feeSanityCheckBaseFeeMultiplier) {
1324
                                            return cb(new blocktrail.WalletFeeError("the fee suggested by the coin selection (" + fee + ") " +
1325
                                                "seems awefully high (" + estimatedFee + ") for FEE_STRATEGY_OPTIMAL"));
1326
                                        }
1327
                                    break;
1328
                                }
1329
                            }
1330
1331
                            cb();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1332
                        }
1333
                    ], function(err) {
1334
                        if (err) {
1335
                            deferred.reject(new blocktrail.WalletSendError(err));
1336
                            return;
1337
                        }
1338
1339
                        deferred.resolve([tx, utxos]);
1340
                    });
1341
1342
                    return deferred.promise;
1343
                }
1344
            )
1345
        );
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1346
    });
1347
1348
    return deferred.promise;
1349
};
1350
1351
1352
/**
1353
 * use the API to get the best inputs to use based on the outputs
1354
 *
1355
 * @param pay               array       {'address': (int)value}     coins to send
1356
 * @param [lockUTXO]        bool        lock UTXOs for a few seconds to allow for transaction to be created
1357
 * @param [allowZeroConf]   bool        allow zero confirmation unspent outputs to be used in coin selection
1358
 * @param [feeStrategy]     string      defaults to FEE_STRATEGY_OPTIMAL
1359
 * @param [options]         object
1360
 * @param [cb]              function    callback(err, utxos, fee, change)
1361
 * @returns {q.Promise}
1362
 */
1363
Wallet.prototype.coinSelection = function(pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1364
    var self = this;
1365
1366
    if (typeof lockUTXO === "function") {
1367
        cb = lockUTXO;
1368
        lockUTXO = true;
1369
    } else if (typeof allowZeroConf === "function") {
1370
        cb = allowZeroConf;
1371
        allowZeroConf = false;
1372
    } else if (typeof feeStrategy === "function") {
1373
        cb = feeStrategy;
1374
        feeStrategy = null;
1375
    } else if (typeof options === "function") {
1376
        cb = options;
1377
        options = {};
1378
    }
1379
1380
    lockUTXO = typeof lockUTXO !== "undefined" ? lockUTXO : true;
1381
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1382
    options = options || {};
1383
1384
    var send;
1385
    try {
1386
        send = Wallet.convertPayToOutputs(pay, self.network);
1387
    } catch (e) {
1388
        var deferred = q.defer();
1389
        deferred.promise.nodeify(cb);
1390
        deferred.reject(e);
1391
        return deferred.promise;
1392
    }
1393
1394
    return self.sdk.coinSelection(self.identifier, send, lockUTXO, allowZeroConf, feeStrategy, options, cb);
1395
};
1396
1397
/**
1398
 * send the transaction using the API
1399
 *
1400
 * @param txHex             string      partially signed transaction as hex string
1401
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
1402
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
1403
 * @param [twoFactorToken]  string      2FA token
1404
 * @param prioboost         bool
1405
 * @param [cb]              function    callback(err, txHash)
1406
 * @returns {q.Promise}
1407
 */
1408
Wallet.prototype.sendTransaction = function(txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
1409
    var self = this;
1410
1411
    if (typeof twoFactorToken === "function") {
1412
        cb = twoFactorToken;
1413
        twoFactorToken = null;
1414
        prioboost = false;
1415
    } else if (typeof prioboost === "function") {
1416
        cb = twoFactorToken;
1417
        prioboost = false;
1418
    }
1419
1420
    var deferred = q.defer();
1421
    deferred.promise.nodeify(cb);
1422
1423
    self.sdk.sendTransaction(self.identifier, txHex, paths, checkFee, twoFactorToken, prioboost)
1424
        .then(
1425
            function(result) {
1426
                deferred.resolve(result);
1427
            },
1428
            function(e) {
1429
                if (e.requires_2fa) {
1430
                    deferred.reject(new blocktrail.WalletMissing2FAError());
1431
                } else if (e.message.match(/Invalid two_factor_token/)) {
1432
                    deferred.reject(new blocktrail.WalletInvalid2FAError());
1433
                } else {
1434
                    deferred.reject(e);
1435
                }
1436
            }
1437
        )
1438
    ;
1439
1440
    return deferred.promise;
1441
};
1442
1443
/**
1444
 * setup a webhook for this wallet
1445
 *
1446
 * @param url           string      URL to receive webhook events
1447
 * @param [identifier]  string      identifier for the webhook, defaults to WALLET- + wallet.identifier
1448
 * @param [cb]          function    callback(err, webhook)
1449
 * @returns {q.Promise}
1450
 */
1451
Wallet.prototype.setupWebhook = function(url, identifier, cb) {
1452
    var self = this;
1453
1454
    if (typeof identifier === "function") {
1455
        cb = identifier;
1456
        identifier = null;
1457
    }
1458
1459
    identifier = identifier || ('WALLET-' + self.identifier);
1460
1461
    return self.sdk.setupWalletWebhook(self.identifier, identifier, url, cb);
1462
};
1463
1464
/**
1465
 * delete a webhook that was created for this wallet
1466
 *
1467
 * @param [identifier]  string      identifier for the webhook, defaults to WALLET- + wallet.identifier
1468
 * @param [cb]          function    callback(err, success)
1469
 * @returns {q.Promise}
1470
 */
1471
Wallet.prototype.deleteWebhook = function(identifier, cb) {
1472
    var self = this;
1473
1474
    if (typeof identifier === "function") {
1475
        cb = identifier;
1476
        identifier = null;
1477
    }
1478
1479
    identifier = identifier || ('WALLET-' + self.identifier);
1480
1481
    return self.sdk.deleteWalletWebhook(self.identifier, identifier, cb);
1482
};
1483
1484
/**
1485
 * get all transactions for the wallet (paginated)
1486
 *
1487
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1488
 * @param [cb]      function    callback(err, transactions)
1489
 * @returns {q.Promise}
1490
 */
1491
Wallet.prototype.transactions = function(params, cb) {
1492
    var self = this;
1493
1494
    return self.sdk.walletTransactions(self.identifier, params, cb);
1495
};
1496
1497
Wallet.prototype.maxSpendable = function(allowZeroConf, feeStrategy, options, cb) {
1498
    var self = this;
1499
1500
    if (typeof allowZeroConf === "function") {
1501
        cb = allowZeroConf;
1502
        allowZeroConf = false;
1503
    } else if (typeof feeStrategy === "function") {
1504
        cb = feeStrategy;
1505
        feeStrategy = null;
1506
    } else if (typeof options === "function") {
1507
        cb = options;
1508
        options = {};
1509
    }
1510
1511
    if (typeof allowZeroConf === "object") {
1512
        options = allowZeroConf;
1513
        allowZeroConf = false;
1514
    } else if (typeof feeStrategy === "object") {
1515
        options = feeStrategy;
1516
        feeStrategy = null;
1517
    }
1518
1519
    options = options || {};
1520
1521
    if (typeof options.allowZeroConf !== "undefined") {
1522
        allowZeroConf = options.allowZeroConf;
1523
    }
1524
    if (typeof options.feeStrategy !== "undefined") {
1525
        feeStrategy = options.feeStrategy;
1526
    }
1527
1528
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1529
1530
    return self.sdk.walletMaxSpendable(self.identifier, allowZeroConf, feeStrategy, options, cb);
1531
};
1532
1533
/**
1534
 * get all addresses for the wallet (paginated)
1535
 *
1536
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1537
 * @param [cb]      function    callback(err, addresses)
1538
 * @returns {q.Promise}
1539
 */
1540
Wallet.prototype.addresses = function(params, cb) {
1541
    var self = this;
1542
1543
    return self.sdk.walletAddresses(self.identifier, params, cb);
1544
};
1545
1546
/**
1547
 * @param address   string      the address to label
1548
 * @param label     string      the label
1549
 * @param [cb]      function    callback(err, res)
1550
 * @returns {q.Promise}
1551
 */
1552
Wallet.prototype.labelAddress = function(address, label, cb) {
1553
    var self = this;
1554
1555
    return self.sdk.labelWalletAddress(self.identifier, address, label, cb);
1556
};
1557
1558
/**
1559
 * get all UTXOs for the wallet (paginated)
1560
 *
1561
 * @param [params]  object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1562
 * @param [cb]      function    callback(err, addresses)
1563
 * @returns {q.Promise}
1564
 */
1565
Wallet.prototype.utxos = function(params, cb) {
1566
    var self = this;
1567
1568
    return self.sdk.walletUTXOs(self.identifier, params, cb);
1569
};
1570
1571
Wallet.prototype.unspentOutputs = Wallet.prototype.utxos;
1572
1573
/**
1574
 * sort list of pubkeys to be used in a multisig redeemscript
1575
 *  sorted in lexicographical order on the hex of the pubkey
1576
 *
1577
 * @param pubKeys   {bitcoin.HDNode[]}
1578
 * @returns string[]
1579
 */
1580
Wallet.sortMultiSigKeys = function(pubKeys) {
1581
    pubKeys.sort(function(key1, key2) {
1582
        return key1.toString('hex').localeCompare(key2.toString('hex'));
1583
    });
1584
1585
    return pubKeys;
1586
};
1587
1588
/**
1589
 * determine how much fee is required based on the inputs and outputs
1590
 *  this is an estimation, not a proper 100% correct calculation
1591
 *
1592
 * @todo: mark deprecated in favor of estimations where UTXOS are known
1593
 * @param {bitcoin.Transaction} tx
1594
 * @param {int} feePerKb when not null use this feePerKb, otherwise use BASE_FEE legacy calculation
1595
 * @returns {number}
1596
 */
1597
Wallet.estimateIncompleteTxFee = function(tx, feePerKb) {
1598
    var size = Wallet.estimateIncompleteTxSize(tx);
1599
    var sizeKB = size / 1000;
1600
    var sizeKBCeil = Math.ceil(size / 1000);
1601
1602
    if (feePerKb) {
1603
        return parseInt(sizeKB * feePerKb, 10);
1604
    } else {
1605
        return parseInt(sizeKBCeil * blocktrail.BASE_FEE, 10);
1606
    }
1607
};
1608
1609
/**
1610
 * Takes tx and utxos, computing their estimated vsize,
1611
 * and uses feePerKb (or BASEFEE as default) to estimate
1612
 * the number of satoshis in fee.
1613
 *
1614
 * @param {bitcoin.Transaction} tx
1615
 * @param {Array} utxos
1616
 * @param feePerKb
1617
 * @returns {Number}
1618
 */
1619
Wallet.estimateVsizeFee = function(tx, utxos, feePerKb) {
1620
    var vsize = SizeEstimation.estimateTxVsize(tx, utxos);
1621
    var sizeKB = vsize / 1000;
1622
    var sizeKBCeil = Math.ceil(vsize / 1000);
1623
1624
    if (feePerKb) {
1625
        return parseInt(sizeKB * feePerKb, 10);
1626
    } else {
1627
        return parseInt(sizeKBCeil * blocktrail.BASE_FEE, 10);
1628
    }
1629
};
1630
1631
/**
1632
 * determine how much fee is required based on the inputs and outputs
1633
 *  this is an estimation, not a proper 100% correct calculation
1634
 *
1635
 * @param {bitcoin.Transaction} tx
1636
 * @returns {number}
1637
 */
1638
Wallet.estimateIncompleteTxSize = function(tx) {
1639
    var size = 4 + 4 + 4 + 4; // version + txinVarInt + txoutVarInt + locktime
1640
1641
    size += tx.outs.length * 34;
1642
1643
    tx.ins.forEach(function(txin) {
1644
        var scriptSig = txin.script,
1645
            scriptType = bitcoin.script.classifyInput(scriptSig);
1646
1647
        var multiSig = [2, 3]; // tmp hardcoded
1648
1649
        // Re-classify if P2SH
1650
        if (!multiSig && scriptType === 'scripthash') {
1651
            var sigChunks = bitcoin.script.decompile(scriptSig);
1652
            var redeemScript = sigChunks.slice(-1)[0];
1653
            scriptSig = bitcoin.script.compile(sigChunks.slice(0, -1));
1654
            scriptType = bitcoin.script.classifyInput(scriptSig);
1655
1656
            if (bitcoin.script.classifyOutput(redeemScript) !== scriptType) {
1657
                throw new blocktrail.TransactionInputError('Non-matching scriptSig and scriptPubKey in input');
1658
            }
1659
1660
            // figure out M of N for multisig (code from internal usage of bitcoinjs)
1661
            if (scriptType === 'multisig') {
1662
                var rsChunks = bitcoin.script.decompile(redeemScript);
1663
                var mOp = rsChunks[0];
1664
                if (mOp === bitcoin.opcodes.OP_0 || mOp < bitcoin.opcodes.OP_1 || mOp > bitcoin.opcodes.OP_16) {
1665
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1666
                }
1667
1668
                var nOp = rsChunks[redeemScript.chunks.length - 2];
1669
                if (mOp === bitcoin.opcodes.OP_0 || mOp < bitcoin.opcodes.OP_1 || mOp > bitcoin.opcodes.OP_16) {
1670
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1671
                }
1672
1673
                var m = mOp - (bitcoin.opcodes.OP_1 - 1);
1674
                var n = nOp - (bitcoin.opcodes.OP_1 - 1);
1675
                if (n < m) {
1676
                    throw new blocktrail.TransactionInputError("Invalid multisig redeemScript");
1677
                }
1678
1679
                multiSig = [m, n];
1680
            }
1681
        }
1682
1683
        if (multiSig) {
1684
            size += (
1685
                32 + // txhash
1686
                4 + // idx
1687
                3 + // scriptVarInt[>=253]
1688
                1 + // OP_0
1689
                ((1 + 72) * multiSig[0]) + // (OP_PUSHDATA[<75] + 72) * sigCnt
1690
                (2 + 105) + // OP_PUSHDATA[>=75] + script
1691
                4 // sequence
1692
            );
1693
1694
        } else {
1695
            size += 32 + // txhash
1696
                4 + // idx
1697
                73 + // sig
1698
                34 + // script
1699
                4; // sequence
1700
        }
1701
    });
1702
1703
    return size;
1704
};
1705
1706
/**
1707
 * determine how much fee is required based on the amount of inputs and outputs
1708
 *  this is an estimation, not a proper 100% correct calculation
1709
 *  this asumes all inputs are 2of3 multisig
1710
 *
1711
 * @todo: mark deprecated in favor of situations where UTXOS are known
1712
 * @param txinCnt       {number}
1713
 * @param txoutCnt      {number}
1714
 * @returns {number}
1715
 */
1716
Wallet.estimateFee = function(txinCnt, txoutCnt) {
1717
    var size = 4 + 4 + 4 + 4; // version + txinVarInt + txoutVarInt + locktime
1718
1719
    size += txoutCnt * 34;
1720
1721
    size += (
1722
            32 + // txhash
1723
            4 + // idx
1724
            3 + // scriptVarInt[>=253]
1725
            1 + // OP_0
1726
            ((1 + 72) * 2) + // (OP_PUSHDATA[<75] + 72) * sigCnt
1727
            (2 + 105) + // OP_PUSHDATA[>=75] + script
1728
            4 // sequence
1729
        ) * txinCnt;
1730
1731
    var sizeKB = Math.ceil(size / 1000);
1732
1733
    return sizeKB * blocktrail.BASE_FEE;
1734
};
1735
1736
/**
1737
 * create derived key from parent key by path
1738
 *
1739
 * @param hdKey     {bitcoin.HDNode}
1740
 * @param path      string
1741
 * @param keyPath   string
1742
 * @returns {bitcoin.HDNode}
1743
 */
1744
Wallet.deriveByPath = function(hdKey, path, keyPath) {
1745
    keyPath = keyPath || (!!hdKey.keyPair.d ? "m" : "M");
1746
1747
    if (path[0].toLowerCase() !== "m" || keyPath[0].toLowerCase() !== "m") {
1748
        throw new blocktrail.KeyPathError("Wallet.deriveByPath only works with absolute paths. (" + path + ", " + keyPath + ")");
1749
    }
1750
1751
    if (path[0] === "m" && keyPath[0] === "M") {
1752
        throw new blocktrail.KeyPathError("Wallet.deriveByPath can't derive private path from public parent. (" + path + ", " + keyPath + ")");
1753
    }
1754
1755
    // if the desired path is public while the input is private
1756
    var toPublic = path[0] === "M" && keyPath[0] === "m";
1757
    if (toPublic) {
1758
        // derive the private path, convert to public when returning
1759
        path[0] = "m";
1760
    }
1761
1762
    // keyPath should be the parent parent of path
1763
    if (path.toLowerCase().indexOf(keyPath.toLowerCase()) !== 0) {
1764
        throw new blocktrail.KeyPathError("Wallet.derivePath requires path (" + path + ") to be a child of keyPath (" + keyPath + ")");
1765
    }
1766
1767
    // remove the part of the path we already have
1768
    path = path.substr(keyPath.length);
1769
1770
    // iterate over the chunks and derive
1771
    var newKey = hdKey;
1772
    path.replace(/^\//, "").split("/").forEach(function(chunk) {
1773
        if (!chunk) {
1774
            return;
1775
        }
1776
1777
        if (chunk.indexOf("'") !== -1) {
1778
            chunk = parseInt(chunk.replace("'", ""), 10) + bitcoin.HDNode.HIGHEST_BIT;
1779
        }
1780
1781
        newKey = newKey.derive(parseInt(chunk, 10));
1782
    });
1783
1784
    if (toPublic) {
1785
        return newKey.neutered();
1786
    } else {
1787
        return newKey;
1788
    }
1789
};
1790
1791
module.exports = Wallet;
1792